Android Frida

Android Frida

Frida实在太常用了,所以结合网上的资料以及自己平时的使用总结一份Frida文档,也方便自己查,主要参考Sakura的这篇文章,后面有新的再总结

Frida Android hook | Sakuraのblog (eternalsakura13.com)

frida环境

直接下载r0Pan /r0env

R0env,里面有配置好的pyenv以及预装的frida

pyenv

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
下载一个3.8.2,下载真的很慢,要慢慢等
pyenv install 3.8.2

pyenv versions
sakura@sakuradeMacBook-Pro:~$ pyenv versions
system
* 3.8.2 (set by /Users/sakura/.python-version)
切换到我们装的
pyenv local 3.8.2
python -V
pip -V
原本系统自带的
python local system
python -V

把整个系统的python切换
python global XXXX

临时禁用pyenv

image-20220531161329831

1
2
3
if command -v pyenv 1>/dev/null 2>&1; then
eval "$(pyenv init -)"
fi

Frida安装

自己手动安装frida,如果直接按下述安装则会直接安装frida和frida-tools的最新版本。

1
2
3
pip install frida-tools
frida --version
frida-ps --version

指定frida的版本去安装

1
2
3
4
pyenv install 3.7.7
pyenv local 3.7.7
pip install frida==12.8.0
pip install frida-tools==5.3.0

老版本frida和对应关系
对应关系很好找

image-20220531161924152

objection

objection是基于frida的,所以objection的版本要跟frida对应上,找版本间的对应关系是去objection的release处,根据发布时间找最接近的

sensepost/objection: 📱 objection - runtime mobile exploration (github.com)

frida-server

下载frida-server并解压,在这里下载frida-server-12.8.0

先adb shell,然后切换到root权限,把之前push进来的frida server改个名字叫fs
然后运行frida

Android手机一般push的通用目录就是/data/local/tmp

如果要监听端口(因为app可能反frida会检测默认端口),就

1
./fs -l 0.0.0.0:8888

frida开发环境

1
2
3
git clone https://github.com/oleavr/frida-agent-example.git
cd frida-agent-example/
npm install

下载上面整个项目,使用vscode打开此工程,在agent文件夹下编写js,会有智能提示。

npm run watch会监控代码修改自动编译生成js文件

python脚本形式的frida脚本注入

1
2
3
4
5
6
7
8
9
10
11
12
import time
import frida

device8 = frida.get_device_manager().add_remote_device("192.168.0.9:8888")
pid = device8.spawn("com.android.settings")
device8.resume(pid)
time.sleep(1)
session = device8.attach(pid)
with open("si.js") as f:
script = session.create_script(f.read())
script.load()
input() #等待输入

Objection操作

Objection也很常用

Objection有个缺陷就是 watch class 的时候回watch很多 但是唯独漏掉了构造函数

手动watch构造函数

1
android hooking watch class_method java.io.File.$init  --dump-args  --dump-return

Frida Java Hook

打印参数、返回值/设置返回值/主动调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class LoginActivity extends AppCompatActivity {
/* access modifiers changed from: private */
public Context mContext;

public void onCreate(Bundle bundle) {
super.onCreate(bundle);
this.mContext = this;
setContentView((int) R.layout.activity_login);
final EditText editText = (EditText) findViewById(R.id.username);
final EditText editText2 = (EditText) findViewById(R.id.password);
((Button) findViewById(R.id.login)).setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
String obj = editText.getText().toString();
String obj2 = editText2.getText().toString();
if (TextUtils.isEmpty(obj) || TextUtils.isEmpty(obj2)) {
Toast.makeText(LoginActivity.this.mContext, "username or password is empty.", 1).show();
} else if (LoginActivity.a(obj, obj).equals(obj2)) {
LoginActivity.this.startActivity(new Intent(LoginActivity.this.mContext, FridaActivity1.class));
LoginActivity.this.finishActivity(0);
} else {
Toast.makeText(LoginActivity.this.mContext, "Login failed.", 1).show();
}
}
});
}

LoginActivity.a(obj, obj).equals(obj2)分析之后可得obj2来自password,由从username得来的obj,经过a函数运算之后得到一个值,这两个值相等则登录成功。
所以这里关键是hook a函数的参数,脚本如下。

1
2
3
4
5
6
7
8
9
10
11
//打印参数、返回值
function Login(){
Java.perform(function(){
Java.use("com.example.androiddemo.Activity.LoginActivity").a.overload('java.lang.String', 'java.lang.String').implementation = function (str, str2){
var result = this.a(str, str2);
console.log("args0:"+str+" args1:"+str2+" result:"+result);
return result;
}
})
}
setImmediate(Login)

观察输入和输出,这里也可以直接主动调用。

1
2
3
4
5
6
7
8
9
function login() {
Java.perform(function () {
console.log("start")
var login = Java.use("com.example.androiddemo.Activity.LoginActivity")
var result = login.a("1234","1234")
console.log(result)
})
}
setImmediate(login)
1
2
3
4
5
...
start
4e4feaea959d426155a480dc07ef92f4754ee93edbe56d993d74f131497e66fb
然后
adb shell input text "4e4feaea959d426155a480dc07ef92f4754ee93edbe56d993d74f131497e66fb"

主动调用静态/非静态函数 以及 设置静态/非静态成员变量的值

主要就是下面三点

  • 静态函数直接use class然后调用方法,非静态函数需要先choose实例然后调用
  • 设置成员变量的值,写法是xx.value = yy,其他方面和函数一样。
  • 如果有一个成员变量和成员函数的名字相同,则在其前面加一个_,如_xx.value = yy

Hook内部类,枚举类的函数并Hook

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class FridaActivity4 extends BaseFridaActivity {
public String getNextCheckTitle() {
return "当前第4关";
}

private static class InnerClasses {
public static boolean check1() {
return false;
}

public static boolean check2() {
return false;
}

public static boolean check3() {
return false;
}

public static boolean check4() {
return false;
}

public static boolean check5() {
return false;
}

public static boolean check6() {
return false;
}

private InnerClasses() {
}
}

public void onCheck() {
if (!InnerClasses.check1() || !InnerClasses.check2() || !InnerClasses.check3() || !InnerClasses.check4() || !InnerClasses.check5() || !InnerClasses.check6()) {
super.CheckFailed();
return;
}
CheckSuccess();
startActivity(new Intent(this, FridaActivity5.class));
finishActivity(0);
}
}

这一关的关键是让if (!InnerClasses.check1() || !InnerClasses.check2() || !InnerClasses.check3() || !InnerClasses.check4() || !InnerClasses.check5() || !InnerClasses.check6())中的所有check全部返回true。

其实这里唯一的问题就是寻找内部类InnerClasses,对于内部类的hook,通过类名$内部类名去use。

手动Hook所有的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function ch4() {
Java.perform(function () {
var InnerClasses = Java.use("com.example.androiddemo.Activity.FridaActivity4$InnerClasses")
console.log("start")
InnerClasses.check1.implementation = function () {
return true
}
InnerClasses.check2.implementation = function () {
return true
}
InnerClasses.check3.implementation = function () {
return true
}
InnerClasses.check4.implementation = function () {
return true
}
InnerClasses.check5.implementation = function () {
return true
}
InnerClasses.check6.implementation = function () {
return true
}
})
}

批量HOOK用getDeclareMethods

1
2
var inner_classes = Java.use("com.example.androiddemo.Activity.FridaActivity4$InnerClasses")
var all_methods = inner_classes.class.getDeclaredMethods();

Hook构造函数

$init来hook构造函数

1
2
3
4
5
Java.use("com.tlamb96.kgbmessenger.b.a").$init.implementation = function (i, str1, str2, z) {
this.$init(i, str1, str2, z)
console.log(i, str1, str2, z)
printStack("com.tlamb96.kgbmessenger.b.a")
}

打印栈回溯

用的就是Java的Exception类的getStackTrace方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function printStack(name) {
Java.perform(function () {
var Exception = Java.use("java.lang.Exception");
var ins = Exception.$new("Exception");
var straces = ins.getStackTrace();
if (straces != undefined && straces != null) {
var strace = straces.toString();
var replaceStr = strace.replace(/,/g, "\\n");
console.log("=============================" + name + " Stack strat=======================");
console.log(replaceStr);
console.log("=============================" + name + " Stack end=======================\r\n");
Exception.$dispose();
}
});
}

Spwan/Attach

frida注入时-f参数代表spawn启动,spawn适用于一些APP在刚启动时就进行的一些操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    /* access modifiers changed from: protected */
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
setContentView((int) R.layout.activity_main);
String property = System.getProperty("user.home");
String str = System.getenv("USER");
if (property == null || property.isEmpty() || !property.equals("Russia")) {
a("Integrity Error", "This app can only run on Russian devices.");
} else if (str == null || str.isEmpty() || !str.equals(getResources().getString(R.string.User))) {
a("Integrity Error", "Must be on the user whitelist.");
} else {
a.a(this);
startActivity(new Intent(this, LoginActivity.class));
}
}
}

因为这个check是在onCreate里,所以app刚启动就自动检查,所以这里需要用spawn的方式去启动frida脚本hook,而不是attach。

这里有两个检查,一个是检查property的值,一个是检查str的值。
分别从System.getPropertySystem.getenv里获取,hook住这两个函数就行。

1
2
3
4
5
6
7
8
9
10
11
function main() {
Java.perform(function () {
Java.use("java.lang.System").getProperty.overload('java.lang.String').implementation = function (str) {
return "Russia";
}
Java.use("java.lang.System").getenv.overload('java.lang.String').implementation = function(str){
return "RkxBR3s1N0VSTDFOR180UkNIM1J9Cg==";
}
})
}
setImmediate(main)

获取类名

1
2
3
4
function getRealClassName(对象) {
const objClass = Java.use("java.lang.Object").getClass.apply(对象);
return Java.use("java.lang.Class").getName.apply(objClass)
}

逻辑就是先用getClass获取类,再用getName获取类名,只不过这里写法是apply来反向绑定对象

注入dex

实例可以参考gson

总结:
编译出dex之后,通过Java.openClassFile("xxx.dex").load()加载,这样我们就可以正常通过Java.use调用里面的方法了。

制作dex的话,一个是可以直接把class文件转成dex,比较懒就是直接写成apk,然后解压提取classes.dex

枚举activity

image-20220531175401090

从objection中得到的灵感

Hook所有的event(按钮事件)

objection-plugins/agent.js at master · hluwa/objection-plugins (github.com)

葫芦娃大佬的脚本,这个脚本非常显功底,hook event首先就得找到要监控的Onclick函数所处的类 ,一般我们都是传一个new的对象给注册的按钮事件

image-20220718223514178

这里脚本中首先截获setOnclickListener得到注册的对象,然后再用getobjClassName获取对象的类名,再监控器OnClick事件,我们平时要用的话,这里可以加上打印调用栈相关的信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
/*
* Author: hluwa <hluwa888@gmail.com>
* HomePage: https://github.com/hluwa
* CreatedTime: 2020/3/8 22:42
* */
var jclazz = null;
var jobj = null;

function getObjClassName(obj) {
if (!jclazz) {
var jclazz = Java.use("java.lang.Class");
}
if (!jobj) {
var jobj = Java.use("java.lang.Object");
}
return jclazz.getName.call(jobj.getClass.call(obj));
}

function watch(obj, mtdName) {
var listener_name = getObjClassName(obj);
var target = Java.use(listener_name);
if (!target || !mtdName in target) {
return;
}
// send("[WatchEvent] hooking " + mtdName + ": " + listener_name);
target[mtdName].overloads.forEach(function (overload) {
overload.implementation = function () {
send("[WatchEvent] " + mtdName + ": " + getObjClassName(this));
return this[mtdName].apply(this, arguments);
};
})


}


rpc.exports = {
onclick: function () {
Java.perform(function () {


Java.use("android.view.View").setOnClickListener.implementation = function (listener) {
if (listener != null) {
watch(listener, 'onClick');
}
return this.setOnClickListener(listener);
};


Java.choose("android.view.View$ListenerInfo", {
onMatch: function (instance) {
instance = instance.mOnClickListener.value;
if (instance) {
watch(instance, 'onClick');
}
},
onComplete: function () {
}
})
})
}
};

打印和参数构造

frida本来就是置于Java虚拟机的,所以各种复杂的打印只要取食于Java本身就行

  • 数组/(字符串)对象数组/gson/Java.array
  • 对象/多态、强转Java.cast/接口Java.register
  • 泛型、List、Map、Set、迭代打印
  • non-ascii 、 child-gating、rpc 上传到PC上打印

frida 对object、[object,object]的打印

gson

1
2
3
Java.openClassFile("/data/local/tmp/r0gson.dex").load();
const gson = Java.use('com.r0ysue.gson.Gson');
console.log(gson.$new().toJson(xxx));

gson的用法也是用frida注入自己的dex的案例

java Array构造

打印hashmap

1
2
3
4
5
6
7
8
9
10
11
Java.perform(function(){
Java.choose("java.util.HashMap",{
onMatch:function(instance){
if(instance.toString().indexOf("ISBN")!= -1){
console.log("instance.toString:",instance.toString());
}
},onComplete:function(){
console.log("search complete!")
}
})
})

去内存里遍历HashMap,toString之后再过滤关键字

更细的操作

image-20220531171626939

Frida Native Hook

Java层的Native函数的hook和主动调用

跟普通Java函数没什么区别,因为Native的函数要在Java层使用,是会首先声明在java层中,只不过加了个native

Hook导出函数

除了Hook直接在Java层注册的Native函数,SO中导出的函数也可以Hook

  • 首先是找到是否so被加载,通过Process.enumerateModules(),这个API可以枚举被加载到内存的modules。

  • 然后通过Module.findBaseAddress(module name)来查找要hook的函数所在的so的基地址,如果找不到就返回null。

  • 然后可以通过findExportByName(moduleName: string, exportName: string): NativePointer来查找导出函数的绝对地址。如果不知道moduleName是什么,可以传入一个null进入,但是会花费一些时间遍历所有的module。如果找不到就返回null。

  • 找到地址之后,就可以拦截function/instruction的执行。通过Interceptor.attach。使用方法见下代码。

  • 另外为了将jstring的值打印出来,可以使用jenv的函数getStringUtfChars,就像正常的写native程序一样。
    Java.vm.getEnv().getStringUtfChars(args[2], null).readCString()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
extern "C"
JNIEXPORT jstring JNICALL
Java_myapplication_example_com_ndk_1demo_MainActivity_stringWithJNI(JNIEnv *env, jobject instance,
jstring context_) {
const char *context = env->GetStringUTFChars(context_, 0);

int context_size = env->GetStringUTFLength(context_);

if (context_size > 0) {
LOGD("%s\n", context);
}

env->ReleaseStringUTFChars(context_, context);

return env->NewStringUTF("sakura1328");
}

hook脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function hook_native() {
// console.log(JSON.stringify(Process.enumerateModules()));
var libnative_addr = Module.findBaseAddress("libnative-lib.so")
console.log("libnative_addr is: " + libnative_addr)

if (libnative_addr) {
var string_with_jni_addr = Module.findExportByName("libnative-lib.so",
"Java_myapplication_example_com_ndk_1demo_MainActivity_stringWithJNI")
console.log("string_with_jni_addr is: " + string_with_jni_addr)
}

Interceptor.attach(string_with_jni_addr, {
onEnter: function (args) {
console.log("string_with_jni args: " + args[0], args[1], args[2])
console.log(Java.vm.getEnv().getStringUtfChars(args[2], null).readCString())
},
onLeave: function (retval) {
console.log("retval:", retval)
console.log(Java.vm.getEnv().getStringUtfChars(retval, null).readCString())
var newRetval = Java.vm.getEnv().newStringUtf("new retval from hook_native");
retval.replace(ptr(newRetval));
}
})
}

getStringUtfChars是得到jstring的c指针,c指针里是字符串的内容,readCString是从一个NativePointer中读取字符串内容

  • readCString([size = -1]), readUtf8String([size = -1]), readUtf16String([length = -1]), readAnsiString([size = -1]): reads the bytes at this memory location as an ASCII, UTF-8, UTF-16, or ANSI string. Supply the optional size argument if you know the size of the string in bytes, or omit it or specify -1 if the string is NUL-terminated. Likewise you may supply the optional length argument if you know the length of the string in characters.

    A JavaScript exception will be thrown if any of the size / length bytes read from the address isn’t readable.

    Note that readAnsiString() is only available (and relevant) on Windows.

Hook Libc

方法是一样的,找到libc.so的模块,再枚举符号就行了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function hook_libc(){
var pthread_create_addr = null;
var symbols = Process.findModuleByName("libc.so").enumerateSymbols();
for(var i = 0;i<symbols.length;i++){
var symbol = symbols[i].name;

if(symbol.indexOf("pthread_create")>=0){
//console.log(symbols[i].name);
//console.log(symbols[i].address);
pthread_create_addr = symbols[i].address;
}

}
console.log("pthread_create_addr,",pthread_create_addr);
Interceptor.attach(pthread_create_addr,{
onEnter:function(args){
console.log("pthread_create_addr args[0],args[1],args[2],args[3]:",args[0],args[1],args[2],args[3]);

},onLeave:function(retval){
console.log("retval is:",retval)
}
})
}

Native 调用栈打印

frida提供了接口

1
2
3
4
5
6
7
Interceptor.attach(f, {
onEnter: function (args) {
console.log('RegisterNatives called from:\n' +
Thread.backtrace(this.context, Backtracer.ACCURATE)
.map(DebugSymbol.fromAddress).join('\n') + '\n');
}
});

打印的效果

1
2
3
4
5
6
7
8
[Google Pixel::myapplication.example.com.ndk_demo]-> RegisterNatives called from:
0x7a100be03c libart.so!0xe103c
0x7a100be038 libart.so!0xe1038
0x79f85699a0 libnative-lib.so!_ZN7_JNIEnv15RegisterNativesEP7_jclassPK15JNINativeMethodi+0x44
0x79f85698e0 libnative-lib.so!JNI_OnLoad+0x90
0x7a102b9fd4 libart.so!_ZN3art9JavaVMExt17LoadNativeLibraryEP7_JNIEnvRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEP8_jobjectP8_jstringPS9_+0x638
0x7a08e3820c libopenjdkjvm.so!JVM_NativeLoad+0x110
0x70b921c4 boot.oat!oatexec+0xa81c4

这是官方文档就这样写的

  • Thread.backtrace([context, backtracer]): generate a backtrace for the current thread, returned as an array of NativePointer objects.

    If you call this from Interceptor’s onEnter or onLeave callbacks you should provide this.context for the optional context argument, as it will give you a more accurate backtrace. Omitting context means the backtrace will be generated from the current stack location, which may not give you a very good backtrace due to the JavaScript VM’s stack frames. The optional backtracer argument specifies the kind of backtracer to use, and must be either Backtracer.FUZZY or Backtracer.ACCURATE, where the latter is the default if not specified. The accurate kind of backtracers rely on debugger-friendly binaries or presence of debug information to do a good job, whereas the fuzzy backtracers perform forensics on the stack in order to guess the return addresses, which means you will get false positives, but it will work on any binary.

Native 方法替换

直接替换掉整个Native方法的话,用的是Interceptor的replace,而不是attach

也是在文档里有对标的

1
2
3
4
5
6
7
8
9
const openPtr = Module.getExportByName('libc.so', 'open');
const open = new NativeFunction(openPtr, 'int', ['pointer', 'int']);
Interceptor.replace(openPtr, new NativeCallback((pathPtr, flags) => {
const path = pathPtr.readUtf8String();
log('Opening "' + path + '"');
const fd = open(pathPtr, flags);
log('Got fd: ' + fd);
return fd;
}, 'int', ['pointer', 'int']));

inline Hook

inline hook就是任意地址hook,前面介绍的interceptor的常规方法都是函数头hook

一下面ARM64代码为例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
.text:000000000000772C ; __unwind {
.text:000000000000772C SUB SP, SP, #0x40
.text:0000000000007730 STP X29, X30, [SP,#0x30+var_s0]
.text:0000000000007734 ADD X29, SP, #0x30
.text:0000000000007738 ; 6: v6 = a1;
.text:0000000000007738 MOV X8, XZR
.text:000000000000773C STUR X0, [X29,#var_8]
.text:0000000000007740 ; 7: v5 = a3;
.text:0000000000007740 STUR X1, [X29,#var_10]
.text:0000000000007744 STR X2, [SP,#0x30+var_18]
.text:0000000000007748 ; 8: v4 = (const char *)_JNIEnv::GetStringUTFChars(a1, a3, 0LL);
.text:0000000000007748 LDUR X0, [X29,#var_8]
.text:000000000000774C LDR X1, [SP,#0x30+var_18]
.text:0000000000007750 MOV X2, X8
.text:0000000000007754 BL ._ZN7_JNIEnv17GetStringUTFCharsEP8_jstringPh ; _JNIEnv::GetStringUTFChars(_jstring *,uchar *)
.text:0000000000007758 STR X0, [SP,#0x30+var_20]
.text:000000000000775C ; 9: if ( (signed int)_JNIEnv::GetStringUTFLength(v6, v5) > 0 )
.text:000000000000775C LDUR X0, [X29,#var_8]
.text:0000000000007760 LDR X1, [SP,#0x30+var_18]

在775C时,x0里保存的是一个指向一个字符串的指针。hook这个指令,然后Memory.readCString(this.context.x0);打印出来,hook脚本如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function inline_hook() {
var libnative_lib_addr = Module.findBaseAddress("libnative-lib.so");
if (libnative_lib_addr) {
console.log("libnative_lib_addr:", libnative_lib_addr);
var addr_775C = libnative_lib_addr.add(0x775C);
console.log("addr_775C:", addr_775C);

Java.perform(function () {
Interceptor.attach(addr_775C, {
onEnter: function (args) {
var name = this.context.x0.readCString()
console.log("addr_775C OnEnter :", this.returnAddress, name);
},
onLeave: function (retval) {
console.log("retval is :", retval)
}
})
})
}
}
setImmediate(inline_hook())

其实看这里就知道,Interceptor.attach是可以用任意地址的,target只要是个NativePointer就行

这里进去之后可以用context获取上下文寄存器,this对象及context如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
/**
* Callback to invoke when an instruction is about to be executed.
*/
type InstructionProbeCallback = (this: InvocationContext, args: InvocationArguments) => void;
type InvocationContext = PortableInvocationContext | WindowsInvocationContext | UnixInvocationContext;

interface PortableInvocationContext {
/**
* Return address.
*/
returnAddress: NativePointer;

/**
* CPU registers. You may also update register values by assigning to these keys.
*/
context: CpuContext;

/**
* OS thread ID.
*/
threadId: ThreadId;

/**
* Call depth of relative to other invocations.
*/
depth: number;

/**
* User-defined invocation data. Useful if you want to read an argument in `onEnter` and act on it in `onLeave`.
*/
[x: string]: any;
}
...
...
interface Arm64CpuContext extends PortableCpuContext {
x0: NativePointer;
x1: NativePointer;
x2: NativePointer;
x3: NativePointer;
x4: NativePointer;
x5: NativePointer;
x6: NativePointer;
x7: NativePointer;
x8: NativePointer;
x9: NativePointer;
x10: NativePointer;
x11: NativePointer;
x12: NativePointer;
x13: NativePointer;
x14: NativePointer;
x15: NativePointer;
x16: NativePointer;
x17: NativePointer;
x18: NativePointer;
x19: NativePointer;
x20: NativePointer;
x21: NativePointer;
x22: NativePointer;
x23: NativePointer;
x24: NativePointer;
x25: NativePointer;
x26: NativePointer;
x27: NativePointer;
x28: NativePointer;

fp: NativePointer;
lr: NativePointer;
}

Native主动调用未导出函数

例子如下

1
2
3
4
5
6
7
8
9
10
11
12
extern "C"
JNIEXPORT jint JNICALL
Java_com_example_ndk_1demo_MainActivity_sakuraWithInt(JNIEnv *env, jobject thiz, jint a, jint b) {
// TODO: implement sakuraWithInt()
return sakura_add(a,b);
}
...
int sakura_add(int a, int b){
int sum = a+b;
LOGD("sakura add a+b:",sum);
return sum;
}

根据NativePointer创建一个NativeFunction就可以直接调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function main() {
var libnative_lib_addr = Module.findBaseAddress("libnative-lib.so");
console.log("libnative_lib_addr is :", libnative_lib_addr);
if (libnative_lib_addr) {
var sakura_add_addr1 = Module.findExportByName("libnative-lib.so", "_Z10sakura_addii");
var sakura_add_addr2 = libnative_lib_addr.add(0x0F56C) ;
console.log("sakura_add_addr1 ", sakura_add_addr1);
console.log("sakura_add_addr2 ", sakura_add_addr2)
}

var sakura_add1 = new NativeFunction(sakura_add_addr1, "int", ["int", "int"]);
var sakura_add2 = new NativeFunction(sakura_add_addr2, "int", ["int", "int"]);

console.log("sakura_add1 result is :", sakura_add1(200, 33));
console.log("sakura_add2 result is :", sakura_add2(100, 133));
}
setImmediate(main())
...
...
libnative_lib_addr is : 0x79fa1c5000
sakura_add_addr1 0x79fa1d456c
sakura_add_addr2 0x79fa1d456c
sakura_add1 result is : 233
sakura_add2 result is : 233

主动加载so并调用函数(SO注入)

Frida反调试

这一节的主要内容就是关于反调试的原理和如何破解反调试,重要内容还是看文章理解即可。
因为我并不需要做反调试相关的工作,所以部分内容略过。

####

Refs

Frida Android hook | Sakuraのblog (eternalsakura13.com)